<?php

namespace Core\Common\Base;

use Core\Common\Argus\Interceptor\ArgusInterceptor;
use Core\Common\Route\Request;
use Core\Common\Route\Response;
use Core\Common\Util\Helper;

class Log
{
    const LOG_HEADERS = [
        //'QUERY_STRING',
        //'REQUEST_METHOD',
        //'REQUEST_TIME_FLOAT',
        //'REMOTE_ADDR',
        //'HTTP_CONTENT_TYPE',
        //'HTTP_USER_AGENT',
        //'HTTP_X_FORWARDED_FOR',
        //'HTTP_X_REAL_IP',
        //'X_SQKB_TRACE_ID',
        //'HTTP_X_SQKB_TRACE_ID'
    ];

    const JSON_FLAG = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR;
    const MARS_SUFFIX = '_mars'; // MARS系统需要在日志或服务名添加的后缀

    /**
     * 获取毫秒级时间
     * @return string
     */
    private static function time()
    {
        list($micro, $second) = explode(' ', microtime());
        return date('Y-m-d', $second) . 'T' . date('H:i:s', $second) . substr($micro, 1, 4) . date('O');
    }

    private static function logLevel($level)
    {
        switch ($level) {
            case LOG_EMERG:
                return 'EMERG';
            case LOG_ALERT:
                return 'ALERT';
            case LOG_CRIT:
                return 'CRITICAL';
            case LOG_ERR:
                return 'ERROR';
            case LOG_WARNING:
                return 'WARNING';
            case LOG_NOTICE:
                return 'NOTICE';
            case LOG_INFO:
                return 'INFO';
            case LOG_DEBUG:
                return 'DEBUG';
            default:
                return $level;
        }
    }

    public static function errno2LogLevel($errorno)
    {
        switch ($errorno) {
            case E_ERROR: // 1 //
                return LOG_ERR;
            case E_WARNING: // 2 //
                return LOG_WARNING;
            case E_PARSE: // 4 //
                return LOG_WARNING;
            case E_NOTICE: // 8 //
                return LOG_NOTICE;
            case E_CORE_ERROR: // 16 //
                return LOG_ERR;
            case E_CORE_WARNING: // 32 //
                return LOG_WARNING;
            case E_COMPILE_ERROR: // 64 //
                return LOG_ERR;
            case E_COMPILE_WARNING: // 128 //
                return LOG_WARNING;
            case E_USER_ERROR: // 256 //
                return LOG_ERR;
            case E_USER_WARNING: // 512 //
                return LOG_WARNING;
            case E_USER_NOTICE: // 1024 //
                return LOG_NOTICE;
            case E_STRICT: // 2048 //
                return LOG_NOTICE;
            case E_RECOVERABLE_ERROR: // 4096 //
                return LOG_ERR;
            case E_DEPRECATED: // 8192 //
                return LOG_NOTICE;
            case E_USER_DEPRECATED: // 16384 //
                return LOG_NOTICE;
        }
        return "";
    }

    public static function currentLogLevel()
    {
        $logConfig = Config::config('log');
        static $logLevel = null;
        if (is_null($logLevel)) {//配置的日志级别
            if (empty($logConfig['level'])) {
                $logLevel = LOG_DEBUG;
            } else {
                if (is_int($logConfig['level']) || ctype_digit($logConfig['level'])) {
                    $logLevel = (int)$logConfig['level'];
                } else {
                    $logLevelName = strtoupper($logConfig['level']);
                    $logLevel = defined($logLevelName) ? constant($logLevelName) : LOG_DEBUG;
                }
            }
        }
        return $logLevel;
    }

    /**
     * @param $values
     * @return string
     * @desc 拼装一行日志, 以[]包裹内容
     */
    private static function msg($level, $moduleName, $values)
    {
        if (defined('CORE_ENV')) {
            $str = self::time() . ' [' . CORE_ENV . ']';
        } else {
            $str = self::time();
        }
        $str .= " [$moduleName]" . ' [' . self::logLevel($level) . '] [' . Request::uri() . ']';
        foreach ($values as $val) {
            if (is_array($val) || is_object($val)) {
                $str .= ' [' . json_encode($val) . ']';
            } else {
                $str .= ' [' . $val . ']';
            }
        }
        $str .= "\n";
        return $str;
    }

    /**
     * @param mixed $level
     * @param $moduleName
     * @param $records
     * @return mixed
     * @desc json格式的日志内容
     */
    private static function json($level, $moduleName, $records)
    {
        $userAgent = Request::userAgent();
        if (false !== strpos($userAgent, 'Consul') || 'HEAD' == Request::server('REQUEST_METHOD')) {
            return null;
        }

        $userId = $tokenType = 0;
        if (class_exists('\Sqkb\Base\Util\UserToken') && method_exists('\Sqkb\Base\Util\UserToken', 'getUserIdAndTokenType')) {
            [$userId, $tokenType] = \Sqkb\Base\Util\UserToken::getUserIdAndTokenType();
        }

        $xForwardFor = (string)Request::server('HTTP_X_FORWARDED_FOR');
        $ipArr = explode(',', $xForwardFor);
        $realRemoteIp = $ipArr ? trim($ipArr[0]) : '';

        $arr = [
            'datetime' => self::time(),
            'env' => CORE_ENV,
            'log_type' => $moduleName,
            'log_level' => self::logLevel($level),
            'app_name' => CORE_NAME,
            'uri' => Request::uri(),
            'status' => Response::getCode(),
            'hn' => Helper::getHostName(),
            'client_id' => (string)Request::request('client_id'),
            'device_id' => (string)Request::request('device_id'),
            'x_sqkb_trace_id' => (string)Request::traceId(),
            'x_forwarded_for' => $xForwardFor,
            'remote_ip' => (string)Request::server('REMOTE_ADDR'),
            'x_sqkb_mars' => (string)(Request::server('HTTP_X_SQKB_MARS') ?? (Request::server('HTTP_X_SQKB_MARS') ?? '')),
            'real_remote_ip' => $realRemoteIp,
            'user_id' => $userId,
            'token_type' => $tokenType,
        ];
        if ($moduleName == 'access' || 'access_mars' == $moduleName) {
            $req = (array)Request::request();
            foreach ($req as $rk => $rv) {
                if ($rk == 'fetch_text' || $rk == 'search_task_result') {
                    $arr['param_' . $rk] = '...';
                    continue;
                }
                $arr['param_' . $rk] = is_scalar($rv) ? "$rv" : (string)json_encode($rv, self::JSON_FLAG);
            }
        }
        foreach ($records as $i => $values) {
            $keys = array_keys($values);
            if ($keys !== array_keys($keys)) {
                foreach ($values as $k => $v) {
                    if (is_scalar($v)) {
                        $arr[$k] = "$v";
                    } else {
                        $arr[$k] = (string)json_encode($v, self::JSON_FLAG);
                    }
                }
            } else {
                $arr['content' . $i] = (string)json_encode($values, self::JSON_FLAG);
            }
        }
        self::influx($moduleName, $arr);

        // argus 日志采样
        if (Request::isArgusSimple()) {
            self::argus($moduleName);
        }
        return (string)json_encode($arr, self::JSON_FLAG) . "\n";
    }

    public static function influx($moduleName, $arr)
    {
        if ('access' !== $moduleName && 'access_mars' != $moduleName) {
            return;
        }

        if (!Config::config('base.influxLog')) {
            return;
        }
        $ms = $arr['ms'] ?? 0;
        $param = '';
        if (Response::httpCode() > 499 && Response::httpCode() < 1000) {
            $param = base64_encode(json_encode(Request::request()));
        }
        $reqSize = Request::requestSize();
        $traceId = Request::traceId();
        $uri = Request::uri();
        $stripUri = Config::config('base.stripUri');
        if ($stripUri && is_array($stripUri)) {
            foreach ($stripUri as $newUri) {
                if (0 === strpos($uri, $newUri)) {
                    $uri = $newUri;
                    break;
                }
            }
        }

        if (Request::isRunningInMars()) {
            $serviceName = 'mars';
        } else {
            $serviceName = 'services';
        }
        $responseSize = isset($arr['response_size']) ? $arr['response_size'] : 0;
        $memoryPeakUsedMB = memory_get_peak_usage(true) / 1024 / 1024;
        $memoryUsedMB = memory_get_usage(true) / 1024 / 1024;
        $ret = [
            $serviceName . ',service=' . CORE_NAME . ',uri=' . $uri . ',response_status=' . Response::httpCode(),
            ' real_ip="' . Helper::getClientIpAdress() . '",ip="' . Helper::getRemoteIpAdress() . '",request_size=' . $reqSize .
            ",response_size=$responseSize,latency_request=" . round(
                $ms,
                3
            ) . ',trace_id="' . $traceId . '",param="' . $param . '",memory_used='.$memoryUsedMB.',memory_used_peak='.$memoryPeakUsedMB.' ',
            number_format(microtime(true) * 1000000000, 0, '', ''),
            "\n",
        ];
        Job::fire('log', $moduleName, implode('', $ret), 'influx_');
    }

    public static function argus($moduleName)
    {
        if ('access' === $moduleName || 'access_mars' == $moduleName) {
            return;
        }

        $logEntry = [
            'trace_id' => Request::traceId(),
            'span_id' => Request::spanId(),
            'event' => $moduleName,
            'server_name' => Helper::getHostName(),
            'service_name' => CORE_NAME,
            'timestamp' => Helper::getMillisecond()
        ];

        ArgusInterceptor::recordLog($logEntry);
    }

    /**
     * 记录日志到influx系统
     * <measurement>[,<tag_key>=<tag_value>[,<tag_key>=<tag_value>]] <field_key>=<field_value>[,<field_key>=<field_value>] [<timestamp>]
     * http://wiki.17gwx.com/pages/viewpage.action?pageId=27952535
     * https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_reference/
     * @param string $service 服务名字
     * @param array $tagSet 索引字段,key-value值(不能太多, value只能是数字或字符串)
     * @param array $fieldSet key-value 值 (value只能是数字或字符串)
     */
    public static function influxMeasureLog($service, $tagSet, $fieldSet)
    {
        $service = trim($service);
        if (empty($service) || empty($fieldSet) || empty($tagSet)) {
            return;
        }

        // 压测流量写不同位置
        if (Request::isRunningInMars()) {
            $service .= self::MARS_SUFFIX;
        }

        $time = number_format(microtime(true) * 1000000000, 0, '', '');
        $retStr = implode('', [
            $service,
            ',',
            self::influxTags($tagSet),
            ' ',
            self::implodeInfluxKeyValue($fieldSet),
            ' ',
            $time,
            "\n"
        ]);
        if ('cli' == php_sapi_name() && Watcher::getInstance()->has('log')) {
            Watcher::getInstance()->fire('log', [$service, $retStr, 'influx_']);
        } else {
            Log::save($service, $retStr, 'influx_');
        }
    }

    private static function influxTags($set)
    {
        if (empty($set)) {
            return '';
        }
        $ret = [];
        foreach ($set as $k => $v) {
            $ret[] = trim($k) . '=' . $v;
        }
        return implode(',', $ret);
    }

    /**
     * 合并数组key-value成一个字符串
     * @param $set
     * @return string
     */
    private static function implodeInfluxKeyValue($set)
    {
        if (empty($set)) {
            return '';
        }

        $ret = [];
        foreach ($set as $k => $v) {
            if (is_numeric($v)) {
                $ret[] = sprintf('%s=%s', trim($k), $v);
            } else {
                $ret[] = sprintf('%s="%s"', trim($k), addslashes(trim($v)));
            }
        }
        return implode(',', $ret);
    }

    /**
     * 保存日志
     * @param string $name
     * @param string $str
     */
    public static function save($name, $str, $prefix = 'flm_')
    {
        $logConfig = Config::config('log');
        if (isset($logConfig['on']) && !$logConfig['on']) {//判断开关
            return;
        }
        if (isset($logConfig['stdout'])) {//判断开关
            echo $str . "\n";
            return;
        } elseif (isset($logConfig['appname']) && class_exists('\SeasLog')) {
            if (defined('CORE_NAME')) {
                $fileName = strtolower($prefix . str_replace('-', '_', CORE_NAME) . '_' . $name);
            } else {
                $fileName = strtolower($prefix . $name . '_');
            }
            try {
                \SeasLog::info(rtrim($str,  "\n"), [], $logConfig['appname'] . '/' . $fileName);
            } catch (\Throwable $ex) {
                $exArr = [
                    \SeasLog::getBasePath(),
                    $logConfig['appname'],
                    $fileName,
                    $str
                ];
                file_put_contents('/home/gwxdata/wwwlogs/17gwx/seaslog_debug.log', json_encode($exArr), LOCK_EX | FILE_APPEND);
            }
            return;
        }
        static $logRoot = null;
        static $writeAble = null;
        static $period = null;
        if (is_null($logRoot)) {//覆盖日志存储路径
            if (!empty($logConfig['path']) && file_exists($logConfig['path'])) {
                $logRoot = rtrim($logConfig['path'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
            } else {
                $logRoot = CORE_LOG_PATH;
            }
        }
        if (is_null($period)) {//日志记录周期
            $period = '';
            if ('month' == $logConfig['period']) {
                $period = '_Ym';
            } else {
                if ('day' == $logConfig['period']) {
                    $period = '_Ymd';
                } else {
                    if ('week' == $logConfig['period']) {
                        $period = '_YW';
                    }
                }
            }
        }
        if (is_null($writeAble)) {
            $writeAble = is_writeable($logRoot);
        }
        $suffix = empty($period) ? '' : date($period);
        if (defined('CORE_NAME')) {
            $path = $logRoot . strtolower($prefix . str_replace('-', '_', CORE_NAME) . '_' . $name . $suffix) . '.log';
        } else {
            $path = $logRoot . strtolower($prefix . $name . '_' . $suffix) . '.log';
        }
        if ($path && $writeAble) {
            file_put_contents($path, $str, LOCK_EX | FILE_APPEND);
        }
    }

    /**
     * 记录和框架相关日志信息
     * @param $data
     */
    public static function coreLog($data)
    {
        echo json_encode([
            "type" => "core-log",
            "data" => $data,
            "time" => microtime(true),
        ]),"\n";
    }

    /**
     *
     * @param string $moduleName 名称，如: mysql, redis, etc...
     * @param array $arguments 要记录的数值
     */
    public static function __callStatic($moduleName, $arguments)
    {
        $logConfig = Config::config('log');
        $level = LOG_DEBUG;
        if (isset($arguments[0]) && is_int($arguments[0]) && $arguments[0] > -1 && $arguments[0] < 8) {
            $level = array_shift($arguments);
        }
        $logLevel = self::currentLogLevel();
        if ($level > $logLevel) {
            return;
        }

        $moduleName = self::getModuleNameForMars($moduleName);
        $prefix = 'flm_';
        if (substr($moduleName, -4) == '_msg') {
            $moduleName = substr($moduleName, 0, -4);
            $str = self::msg($level, $moduleName, $arguments);
            $prefix = 'msg_';
        } else {
            if (!Request::inDebug() && !empty($logConfig['format']) && 'json' == $logConfig['format']) {//不是调试模式, 且输出格式是json
                $str = self::json($level, $moduleName, $arguments);
            } else {
                $str = self::msg($level, $moduleName, $arguments);
                $prefix = 'msg_';
            }
        }

        if (Request::inDebug()) {
            if ('cli' != php_sapi_name()) {
                echo $str;
            } else {
                Response::debug($str);
            }
            return;
        }

        if ('cli' == php_sapi_name() && Watcher::getInstance()->has('log')) {
            Watcher::getInstance()->fire('log', [$moduleName, $str, $prefix]);
        } else {
            self::save($moduleName, $str, $prefix);
        }
    }

    /**
     * 获取压测流量对应的日志文件名
     * @param $moduleName
     * @return string
     */
    private static function getModuleNameForMars($moduleName)
    {
        if (!Request::isRunningInMars()) {
            return $moduleName;
        }

        $suffix = substr($moduleName, 0, -4);
        if ('_msg' == $suffix) {
            $moduleName = substr($moduleName, -4) . '_mars_msg';
        } else {
            $moduleName .= self::MARS_SUFFIX;
        }

        return $moduleName;
    }
}
